function createTextNode(text) {
    return {
        type: "TEXT_ELEMENT",
        props: {
            nodeValue: text,
            children: []
        }
    }
}

function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map((child) => {
                const isTextNode = typeof child === "string" || typeof child === "number";
                return isTextNode ? createTextNode(child) : child
            })
        }
    }
}
function render(el, container) {
    wipRoot = {
        dom: container,
        props: {
            children: [el]
        }
    }
    nextWorkOfUnit = wipRoot
}

let wipRoot = null
let currentRoot = null
let nextWorkOfUnit = null
let deletions = []
let wipFiber = null
function workLoop(deadline) {
    let shouldYield = false
    while (!shouldYield && nextWorkOfUnit) {
        nextWorkOfUnit = preforWorkOfUnit(nextWorkOfUnit)

        if (wipRoot?.sibling?.type === nextWorkOfUnit?.type) {
            console.log('hit', wipRoot, nextWorkOfUnit);
            nextWorkOfUnit = void undefined
        }

        shouldYield = deadline.timeRemaining() < 1
    }

    if (!nextWorkOfUnit && wipRoot) {
        commitRoot()
    }

    requestIdleCallback(workLoop)
}

function commitRoot() {
    deletions.forEach(commitDeletion)
    commitWork(wipRoot.child)
    commitEffectHooks()
    currentRoot = wipRoot
    wipRoot = null
    deletions = []
    isMounted = false
}

function commitEffectHooks() {
    function run(fiber) {
        if (!fiber) return

        if (!fiber.alternate) {
            // init
            fiber.effectHooks?.forEach(hook => {
                hook.cleanup = hook?.callback()
            })
        } else {
            // update - deps 有没有发生变化
            fiber.effectHooks?.forEach((newHook, index) => {
                if (newHook.deps.length > 0) {
                    const oldEffectHook = fiber.alternate?.effectHooks[index]

                    const needUpdate = oldEffectHook?.deps.some((oldDep, i) => {
                        return oldDep !== newHook.deps[i]
                    })

                    needUpdate && (newHook.cleanup = newHook?.callback())
                }
            })

        }
        run(fiber.child)
        run(fiber.sibling)
    }

    function runCleanup(fiber) {
        if (!fiber) return

        fiber.alternate?.effectHooks?.forEach(hook => {
            if (hook.deps.length > 0) {
                hook.cleanup?.()
            }
        })

        runCleanup(fiber.child)
        runCleanup(fiber.sibling)
    }

    runCleanup(wipRoot)
    run(wipRoot)
}

let isMounted = true
function commitDeletion(fiber) {
    if (fiber.dom) {
        let fiberParent = fiber.parent
        while (!fiberParent.dom) {
            fiberParent = fiberParent.parent
        }
        fiberParent.dom.removeChild(fiber.dom)
    } else {
        commitDeletion(fiber.child)
    }
}

function commitWork(fiber) {
    if (!fiber) return

    let fiberParent = fiber.parent
    while (!fiberParent.dom) {
        fiberParent = fiberParent.parent
    }

    if (fiber.effectTag === "update") {
        updateProps(fiber.dom, fiber.props, fiber.alternate?.props)
    } else if (fiber.effectTag === "placement") {
        if (fiber.dom) {

            if (isMounted) {
                fiber.isMounted = true
                fiberParent.dom.append(fiber.dom)
            } else {
                let anchor = null
                let nextFiber = fiber

                while (nextFiber.sibling && !nextFiber.sibling.isMounted) {
                    nextFiber = nextFiber.sibling
                }
                if (nextFiber.sibling && nextFiber.sibling.isMounted) {
                    anchor = nextFiber.sibling
                }

                fiberParent.dom.insertBefore(fiber.dom, anchor ? anchor.dom : null)
            }

        }
    }

    commitWork(fiber.child)
    commitWork(fiber.sibling)
}

function createDom(type) {
    return type === "TEXT_ELEMENT"
        ? document.createTextNode("")
        : document.createElement(type)
}

function updateProps(dom, nextProps, prevProps = {}) {

    // 1、olg有 new没有 -> 删除
    Object.keys(prevProps).forEach(key => {
        if (key !== "children") {
            if (!(key in nextProps)) {
                dom.removeAttribute(key)
            }
        }
    })
    // 2、new有 old没有 -> 添加
    // 3、new有 old有   -> 修改
    Object.keys(nextProps).forEach((key) => {
        if (key !== "children") {
            if (nextProps[key] !== prevProps[key]) {
                if (key.startsWith("on")) {
                    const eventType = key.slice(2).toLowerCase()

                    dom.removeEventListener(eventType, prevProps[key])
                    dom.addEventListener(eventType, nextProps[key])
                } else {
                    dom[key] = nextProps[key]
                }
            }
        }
    })
}

function reconcileChildren(fiber, children) {
    let oldFiber = fiber.alternate?.child
    let prevChild = null
    children.forEach((child, index) => {
        const isSameType = oldFiber && oldFiber.type === child.type
        let newFiber
        if (isSameType) {
            // update
            newFiber = {
                type: child.type,
                props: child.props,
                child: null,
                parent: fiber,
                sibling: null,
                dom: oldFiber.dom,
                effectTag: 'update',
                alternate: oldFiber,
                isMounted: oldFiber.isMounted || false,
            }
        } else {
            if (child) {
                newFiber = {
                    type: child.type,
                    props: child.props,
                    child: null,
                    parent: fiber,
                    sibling: null,
                    dom: null,
                    effectTag: 'placement',
                    isMounted: false
                }
            }


            if (oldFiber) {
                console.log("should delete", oldFiber)
                deletions.push(oldFiber)
            }
        }

        if (oldFiber) {
            oldFiber = oldFiber.sibling
        }

        if (index === 0) {
            fiber.child = newFiber
        } else {
            prevChild.sibling = newFiber
        }

        if (newFiber) {
            prevChild = newFiber
        }
    })

    while (oldFiber) {
        deletions.push(oldFiber)
        oldFiber = oldFiber.sibling
    }
}

function updateFunctionComponent(fiber) {
    stateHooks = []
    stateHookIndex = 0
    effectHooks = []
    wipFiber = fiber
    // 转换链表，设置好指针
    const children = [fiber.type(fiber.props)]
    reconcileChildren(fiber, children)
}

function updateHostComponent(fiber) {
    if (!fiber.dom) {
        const dom = (fiber.dom = createDom(fiber.type))

        // 处理 props
        updateProps(dom, fiber.props, {})
    }
    const children = fiber.props.children
    reconcileChildren(fiber, children)
}

function preforWorkOfUnit(fiber) {
    const isFunctionComponent = fiber.type instanceof Function
    if (isFunctionComponent) {
        updateFunctionComponent(fiber)
    } else {
        updateHostComponent(fiber)
    }

    // 返回下一个要执行的任务
    if (fiber.child) {
        return fiber.child
    }

    let nextFiber = fiber
    while (nextFiber) {
        if (nextFiber.sibling) return nextFiber.sibling
        nextFiber = nextFiber.parent
    }
}

requestIdleCallback(workLoop)

function update() {
    let currentFiber = wipFiber

    return () => {
        console.log(currentFiber);
        wipRoot = {
            ...currentFiber,
            alternate: currentFiber
        }
        nextWorkOfUnit = wipRoot
    }
}

let stateHooks
let stateHookIndex
function useState(initial = null) {
    let currentFiber = wipFiber
    const oldHook = currentFiber.alternate?.stateHooks[stateHookIndex]
    const stateHook = {
        state: oldHook ? oldHook.state : initial,
        queue: oldHook ? oldHook.queue : [],
    }

    stateHook.queue.forEach((action) => {
        stateHook.state = action(stateHook.state)
    })

    stateHook.queue = []

    stateHookIndex++
    stateHooks.push(stateHook)
    currentFiber.stateHooks = stateHooks

    function setState(action) {
        const eagerState = typeof action === 'function' ? action : () => action
        if (eagerState(stateHook.state) === stateHook.state) return

        stateHook.queue.push(eagerState)

        wipRoot = {
            ...currentFiber,
            alternate: currentFiber
        }
        nextWorkOfUnit = wipRoot
    }

    return [stateHook.state, setState]
}

let effectHooks
function useEffect(callback, deps) {
    const effectHook = {
        callback,
        deps,
        cleanup: void undefined
    }
    effectHooks.push(effectHook)

    wipFiber.effectHooks = effectHooks
}

const React = {
    update,
    render,
    createElement,
    useState,
    useEffect,
}

export default React